#region Imported Namespaces
using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Text.RegularExpressions;

using BO = PHSRAG.Insight21.BusinessObjects;
using DataAccess = PHSRAG.Utility.DataAccess;
using ICachePolicy = PHSRAG.Insight21.Policy.ICachePolicy;
using SearchCriteria = PHSRAG.Insight21.SearchContext.SearchCriteria;
using PHSRAG.Insight21.InsightUtilities;
#endregion

namespace PHSRAG.Insight21.Humans
{
	/// <summary>
	/// The PendingApplicationSummaries object abstracts the notion of a pending application summary. 
	/// It supports loading of summary information for all pending applications on protocols to 
	/// which the user has access.
	/// </summary>
	public class PendingApplicationSummaries : BO.BusinessObject
	{
		#region Constants
		private const string	PendingAppsSummariesKey = "pendingAppsSummaries_574E3124-0394-4277-889C-613D4151ED2B";
		private const bool		AscendingSortSequence = true;
		private const string	FilteredDataTableName = "filtered";
		private const string	OriginalDataTableName = "original";
		private const string	FeedDataTableName = "feed";
		#endregion

		#region Instance Variables
		private Hashtable		pendingAppsSummaryMap = new Hashtable();
		private string			currentDataTableName = OriginalDataTableName;
		private bool			sortAscending = AscendingSortSequence;
		private string			sortColumn = "ProtocolNumber";	// Keep this in sync with what the stored proc orders by.
		#endregion

		#region Constructor
		
		/// <summary>
		/// Initilize the base class with the specified cache policy.
		/// </summary>
		/// <param name="cachePolicy"></param>
		public PendingApplicationSummaries(ICachePolicy cachePolicy) : base(cachePolicy)
		{
		
		}

		#endregion

		#region Public Properties
		/// <summary>
		/// Get the key to be used in all caching operations for this instance of the object.
		/// </summary>
		public static string CacheKey	{ get { return PendingAppsSummariesKey; } }

		/// <summary>
		/// Get the total number of agreements.
		/// </summary>
		public int Count
		{
			get { return (DataContainer.Tables.Count == 0) ? 0 : 
					  DataContainer.Tables[OriginalDataTableName].Rows.Count; }
		}

		public int FilteredCount
		{
			get { return (DataContainer.Tables.Count == 0) ? 0 :
					  DataContainer.Tables[FilteredDataTableName].Rows.Count; }
		}

		/// <summary>
		/// Get the sort sequence.
		/// </summary>
		public bool SortAscending
		{
			get { return sortAscending; }
		}

		/// <summary>
		/// Get the name of the sort column.
		/// </summary>
		public string SortColumn
		{
			get { return sortColumn; }
		}

		/// <summary>
		/// Date of the last successful feed that ran to populate this data
		/// </summary>
		public string LastFeedRunDate
		{
			get
			{
				return ((DateTime) DataContainer.Tables[FeedDataTableName].
					Rows[0]["LastFeedRunDate"]).ToString("MM/dd/yyyy"); 
			}
		}
		#endregion

		#region BusinessObject Methods
		/// <summary>
		/// Cache the current instance using the prevalent cache policy.
		/// </summary>
		public override void Cache()
		{
			CachePolicy.Cache(CacheKey, this);
		}

		/// <summary>
		/// Load the current instance with summary data for the current user.
		/// </summary>
		/// <param name="connectionString">Database connection string</param>
		/// <param name="args">Is expected to contain a single argument - the domain user ID</param>
		public override void Load(string connectionString, params object[] args)
		{
			if (args.Length != 1)
			{
				throw new Exception("PendingApplicationSummaries load requires a domain user ID");
			}
			else  
			{
				pendingAppsSummaryMap.Clear();
			}			
			DataContainer =	DataAccess.GetDataSet(connectionString, "GetPendingItems",
				DataAccess.BuildSqlParameterArray("@domainUserID", SqlDbType.Int, (int)args[0]));
			
			if (DataContainer.Tables.Count == 2)
			{
				DataContainer.Tables[(int)TableIndex.PendingApplicationSummaries].TableName = OriginalDataTableName;
				DataContainer.Tables[(int)TableIndex.FeedData].TableName = FeedDataTableName;
			}

			else 
				throw new Exception("Stored Procedure returned incorrect number of tables in resultset");
		}

		/// <summary>
		/// Remove the current instance from the cache using the prevalent cache policy.
		/// </summary>
		public override void UnCache()
		{
			CachePolicy.UnCache(CacheKey);
		}

		#endregion

		#region Public Methods
		/// <summary>
		/// Get the list of pendingapplicationsummary objects bound by a start and end index. These indeces are into the list of agreement
		/// summaries that was loaded during the Load() method execution into the data container. Typically, the start index
		/// is on a "page" boundary, where the "page" referes to a datagrid page.
		/// Note that the total number of pendingapplicationsummary objects returned may be less than the requested count.
		/// We maintain a Hashtable of ArrayList objects using the start index as the key. This allows us to retrieve the
		/// set of pendingapplicationsummary objects associated with that index, thereby directly supporting random access based the
		/// page index.
		/// If such an ArrayList does not exist for the current page index, we load them using the list of agreement IDs.
		/// The start end end indeces into that list are computed based on the current page index and the datagrid page size.
		/// Pendingapplicationsummary objects are loaded based on the summaries which falls within the compute start and end indeces, 
		/// and saved in an ArrayList. That ArrayList is then stored in the Hashtable with the current page index as its
		/// key.
		/// </summary>
		/// <param name="startIndex">Index within the pendingapplicationsummary list to begin at</param>
		/// <param name="count">Total number of pendingapplicationsummary objects to load</param>
		/// <returns>The list of pendingapplicationsummary objects</returns>
		public ArrayList GetPendingApplicationsList(int startIndex, int count)
		{
			ArrayList pendingAppsList = pendingAppsSummaryMap[startIndex] as ArrayList;
			if (pendingAppsList == null)
			{
				pendingAppsList = new ArrayList();
				DataTable dataTable = DataContainer.Tables[currentDataTableName];
				for (int i = startIndex, end = Math.Min(i + count, dataTable.Rows.Count); i < end; ++i)
					pendingAppsList.Add(new PendingApplicationSummary(CachePolicy, dataTable.Rows[i]));
				pendingAppsSummaryMap[startIndex] = pendingAppsList;
			}
			return pendingAppsList;
		}

		/// <summary>
		/// Search the DataContainer for rows which match the specified search criteria.
		/// Note that we maintain two DataTables within the DataContainer; a full-complement DataTable and a filtered one.
		/// This allows us to quickly apply various search criteria without having to hit the database. If a filter is in
		/// effect, we simply set the value of the currentTableIndex to DataTableIndex.Filtered so that the
		/// GetAgreementList() method may retreive from the appropriate DataTable.
		/// </summary>
		/// <param name="searchCriteria"></param>
		/// <remarks>True if the search criteria did any filtering; false otherwise</remarks>
		public bool Search(SearchCriteria searchCriteria)
		{
			pendingAppsSummaryMap.Clear();

			StringBuilder filter = new StringBuilder();
			foreach (string key in searchCriteria.Keys)
			{
				switch (key.ToLower())
				{
					case "protocolnumber":
						filter.AppendFormat(
							"{0} ProtocolNumber like '{1}*'", (filter.Length > 0) ? 
							"AND" : 
							"", 
							searchCriteria[key].Replace("'", "''"));
						break;

					case "protocoltitle":
						filter.AppendFormat(
							"{0} Title like '{1}*'", (filter.Length > 0) ? 
							"AND" : 
							"", 
							searchCriteria[key].Replace("'", "''"));
						break;

					case "protocolsponsor":
						filter.AppendFormat(
							"{0} SponsorName like '{1}*'", (filter.Length > 0) ? 
							"AND" : 
							"", 
							searchCriteria[key].Replace("'", "''"));
						break;

					case "version":
						filter.AppendFormat(
							"{0} Version like '{1}*'", (filter.Length > 0) ? 
							"AND" : 
							"", 
							searchCriteria[key].Replace("'", "''"));
						break;

					case "expirationdateto":
						filter.AppendFormat(
							"{0} expirationdate <= '{1}'", (filter.Length > 0) ? 
							"AND" : 
							"", 
							searchCriteria[key]);
						break;

					case "expirationdatefrom":
						filter.AppendFormat(
							"{0} expirationdate >= '{1}'", (filter.Length > 0) ? 
							"AND" : 
							"", 
							searchCriteria[key]);
						break;

					case "pi":
						filter.AppendFormat(
							"{0} PiPersonId ='{1}'", (filter.Length > 0) ? "AND" : "", searchCriteria[key]);
						break;

					case "cos":
									
						// COS passes back BusinessUnit,Name,and Id in the form of 
						// BusinessUnit-Name:Id with multiple COSs separated by |
						// We only want the IDs, in a comma delimited list.
						string[] chiefCodeIds = searchCriteria[key].Split('|');
						Regex regEx = new Regex(@"([^:]+:)(?<id>.*)", RegexOptions.Compiled);
						System.Text.StringBuilder sb = new System.Text.StringBuilder();
				
						for (int i = 0; i < chiefCodeIds.Length; ++i)
						{
							Match match = regEx.Match(chiefCodeIds[i]);
							if (match.Success)
								sb.AppendFormat("{0}{1}", sb.Length > 0 ? "," : "", match.Groups["id"].Value);									 
						}

						filter.AppendFormat(
							"{0} OrganizationId IN ({1})", (filter.Length > 0) ? "AND" : "", sb);
						break;
				}
			}

			bool applySearch = (filter.Length > 0);
			if (applySearch)
			{
				DataTable dataTable = DataContainer.Tables[OriginalDataTableName];
				DataTable filteredDataTable = dataTable.Clone();
				filteredDataTable.TableName = currentDataTableName = FilteredDataTableName;

				DataRow[] filteredRows = dataTable.Select(filter.ToString());
				foreach (DataRow row in filteredRows)
					filteredDataTable.ImportRow(row);

				if (DataContainer.Tables.Contains(FilteredDataTableName))
					DataContainer.Tables.Remove(FilteredDataTableName);
				DataContainer.Tables.Add(filteredDataTable);
			}
			else
				currentDataTableName = OriginalDataTableName;

			return applySearch;
		}

		/// <summary>
		/// Sort the list of agreement summaries by the specified column.
		/// If the column is to be sorted is the same as what was last sorted on, the sort sequence is toggled. 
		/// Else, the sort sequence is set to ascending.
		/// </summary>
		/// <param name="columnName">Column to sort by</param>
		public void Sort(string columnName)
		{
			pendingAppsSummaryMap.Clear();

			sortAscending = (sortColumn == columnName) ? !sortAscending : AscendingSortSequence;
			sortColumn = columnName;

			DataTable dataTable = DataContainer.Tables[currentDataTableName];
			DataView dataView = dataTable.DefaultView;
			dataView.Sort = string.Format("{0} {1}", columnName, sortAscending ? "ASC" : "DESC");

			DataTable sortedDataTable = dataTable.Clone();
			foreach (DataRowView rowView in dataView)
				sortedDataTable.ImportRow(rowView.Row);

			DataContainer.Tables.Remove(currentDataTableName);
			DataContainer.Tables.Add(sortedDataTable);
		}

		/// <summary>
		/// Returns a dataTable of applications for use when downloading to an application
		/// such as excel or pdf
		/// </summary>
		/// <returns></returns>
		public DataTable GetApplicationsForDownload()
		{
			return DataContainer.Tables[currentDataTableName];
		}
		#endregion

		#region TableIndex Enum

		/// <summary>
		/// Enumeration for the tables returned in the load from the database
		/// </summary>
		public enum TableIndex
		{
			PendingApplicationSummaries,
			FeedData
		}

		#endregion
	}
}
